Luciano Magalhães
Agosto, 2024
Ciência de Dados
A ciência de dados abrange um conjunto diversificado de técnicas e ferramentas que permitem a extração de conhecimento e insights valiosos a partir de dados. Python, com sua vasta coleção de bibliotecas, é uma escolha popular para cientistas de dados. Além dos módulos comuns como NumPy, Pandas, Matplotlib e Scikit-learn, existem outros módulos que podem ser utilizados para análises mais avançadas e específicas.
Neste artigo apresento alguns módulos adicionais para ciência de dados utilizando o conjunto de dados House Prices do Kaggle. Esses módulos incluem:
Dask: Para manipulação e análise de dados em grande escala.
XGBoost: Para modelos de gradient boosting.
SHAP: Para interpretar modelos de machine learning.
Plotly: Para visualizações interativas.
TensorFlow: Para deep learning.
# suprimir avisos (warnings)
import warnings
warnings.filterwarnings("ignore")
Quando lidamos com grandes volumes de dados, o Pandas pode não dar conta do recado, especialmente quando a memória é limitada. É aqui que o Dask entra em cena. Imagine dividir seu conjunto de dados em blocos menores e trabalhar com eles como se fosse o Pandas, mas com uma vantagem: o Dask distribui esse trabalho em vários núcleos do seu processador, ou até em diferentes máquinas. Isso significa que você pode processar grandes bases de dados sem se preocupar com a falta de memória.
Exemplo:
No exemplo abaixo, usamos o Dask para carregar e analisar um conjunto de dados de forma eficiente, mostrando como ele calcula estatísticas descritivas rapidamente, mesmo com grandes volumes de informação:
import dask.dataframe as dd
# URL do conjunto de dados
url = "https://raw.githubusercontent.com/ageron/handson-ml/master/datasets/housing/housing.csv"
# Carregando o conjunto de dados usando Dask
dados_casas_dask = dd.read_csv(url)
# Calculando estatísticas descritivas
descricao_dask = dados_casas_dask.describe()
# executa operações de forma eficiente em grandes conjuntos de dados
descricao_dask_computed = descricao_dask.compute()
# Exibindo o resultado das estatísticas descritivas
print(descricao_dask_computed)
longitude latitude housing_median_age total_rooms \
count 20640.000000 20640.000000 20640.000000 20640.000000
mean -119.569704 35.631861 28.639486 2635.763081
std 2.003532 2.135952 12.585558 2181.615252
min -124.350000 32.540000 1.000000 2.000000
25% -121.800000 33.930000 18.000000 1447.750000
50% -118.490000 34.260000 29.000000 2127.000000
75% -118.010000 37.710000 37.000000 3148.000000
max -114.310000 41.950000 52.000000 39320.000000
total_bedrooms population households median_income \
count 20433.000000 20640.000000 20640.000000 20640.000000
mean 537.870553 1425.476744 499.539680 3.870671
std 421.385070 1132.462122 382.329753 1.899822
min 1.000000 3.000000 1.000000 0.499900
25% 296.000000 787.000000 280.000000 2.563400
50% 435.000000 1166.000000 409.000000 3.534800
75% 647.000000 1725.000000 605.000000 4.743250
max 6445.000000 35682.000000 6082.000000 15.000100
median_house_value
count 20640.000000
mean 206855.816909
std 115395.615874
min 14999.000000
25% 119600.000000
50% 179700.000000
75% 264725.000000
max 500001.000000
Utilizando o método compute() em Dask, as operações que foram configuradas de forma paralela são executadas e o resultado é coletado. Isso difere do Pandas, onde todas as operações são realizadas de forma sequencial e em um único núcleo. O Dask, portanto, é ideal para cenários em que o tempo de processamento e a capacidade de memória são críticos, como em grandes bases de dados corporativas.
Benefícios Adicionais:
Dask também é altamente compatível com outras bibliotecas do ecossistema Python, como NumPy e Scikit-learn, facilitando a integração em pipelines de dados já existentes. Além disso, sua capacidade de escalar de um laptop para um cluster distribuído sem alterar o código subjacente o torna uma ferramenta flexível e poderosa para cientistas de dados.
Ao usar o Dask, você aproveita melhor os recursos do seu computador, garantindo que as análises sejam feitas de maneira eficiente, sem comprometer a performance, mesmo com grandes quantidades de dados.
Gradient Boosting é uma técnica que combina o aprendizado sequencial de modelos fracos, geralmente árvores de decisão, para construir um modelo forte. No XGBoost, cada árvore é treinada para corrigir os erros do conjunto anterior, e as previsões são ajustadas iterativamente. A importância dessa abordagem é que ela minimiza o erro ao longo das iterações, resultando em um modelo final que é uma combinação ponderada de todas as árvores. Isso o torna altamente eficaz para tarefas como regressão e classificação, especialmente em conjuntos de dados com ruído e outliers.
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import pandas as pd
# Carregando o conjunto de dados usando pandas
dados_casas = pd.read_csv(url)
# Preparando os dados
X = dados_casas[["total_rooms"]]
y = dados_casas["median_house_value"]
X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, test_size=0.3, random_state=42)
# Treinando um modelo XGBoost
modelo_xgb = xgb.XGBRegressor(objective ='reg:squarederror', n_estimators=100)
modelo_xgb.fit(X_treino, y_treino)
# Faz previsões
y_pred_xgb = modelo_xgb.predict(X_teste)
# Avaliando o modelo
rmse = mean_squared_error(y_teste, y_pred_xgb, squared=False)
print("RMSE do Modelo XGBoost:", rmse)
RMSE do Modelo XGBoost: 113329.45682157896
Gradient Boosting é uma técnica utilizada para melhorar a precisão de modelos preditivos combinando vários modelos simples, como árvores de decisão. No XGBoost, cada árvore é treinada para corrigir os erros das previsões anteriores, resultando em um modelo final que é uma soma ponderada de todas as árvores. Essa metodologia é eficaz para lidar com dados complexos, onde ruídos e outliers podem dificultar a precisão das previsões.
Ao aplicar o XGBoost, é fundamental ajustar os hiperparâmetros, como a profundidade das árvores (max_depth), a taxa de aprendizado (learning_rate), e o número de iterações (n_estimators). Esses ajustes garantem que o modelo não apenas capture as nuances dos dados, mas também evite o overfitting, mantendo a capacidade de generalização para novos dados.
No código apresentado, vimos como o ajuste desses hiperparâmetros, utilizando o GridSearchCV, resultou em um modelo otimizado com um RMSE de 112555.30. Esse processo de otimização é essencial para maximizar a performance do XGBoost, garantindo que ele seja uma ferramenta eficaz tanto para regressão quanto para classificação, especialmente em conjuntos de dados desafiadores.
from sklearn.model_selection import GridSearchCV
# Defini os hiperparâmetros que serão otimizados
param_grid = {
'learning_rate': [0.01, 0.1, 0.2],# Taxa de aprendizado, controla o quanto cada nova árvore contribui para corrigir os erros das árvores anteriores
'max_depth': [3, 5, 7], # Profundidade máxima das árvores, controlando a complexidade do modelo
'subsample': [0.6, 0.8, 1.0], # Fração das amostras usadas para treinar cada árvore. Usar menos de 1.0 (100%) ajuda a prevenir overfitting
'n_estimators': [100, 200, 300] # Número de árvores a serem construídas no modelo, controlando o número de iterações
}
# Instancia o modelo XGBoost
modelo_xgb = xgb.XGBRegressor(objective='reg:squarederror', random_state=42)
# Configura o GridSearchCV / validação cruzada com 5 divisões, e critério de avaliação será o erro quadrático médio negativo - scoring
grid_search = GridSearchCV(estimator=modelo_xgb, param_grid=param_grid, cv=5, scoring='neg_mean_squared_error', verbose=1)
# Executa a busca pelos melhores hiperparâmetros
grid_search.fit(X_treino, y_treino)
# Exibi os melhores parâmetros encontrados
print("Melhores Hiperparâmetros:", grid_search.best_params_)
# Treina o modelo com os melhores parâmetros
modelo_otimizado = grid_search.best_estimator_
# Faz previsões e avalia o modelo otimizado
y_pred_otimizado = modelo_otimizado.predict(X_teste)
rmse_otimizado = mean_squared_error(y_teste, y_pred_otimizado, squared=False)
print("RMSE do Modelo Otimizado:", rmse_otimizado)
Fitting 5 folds for each of 81 candidates, totalling 405 fits
Melhores Hiperparâmetros: {'learning_rate': 0.01, 'max_depth': 3, 'n_estimators': 200, 'subsample': 0.6}
RMSE do Modelo Otimizado: 112555.30074019264
Após a execução do GridSearchCV, o modelo XGBoost foi ajustado com os melhores hiperparâmetros encontrados: learning_rate: 0.01, max_depth: 3, n_estimators: 200, subsample: 0.6. Esses ajustes foram fundamentais para melhorar a performance do modelo, resultando em um RMSE de 112555.30. Esse processo de otimização é essencial para garantir previsões mais precisas, refinando o modelo para melhor atender às especificidades do conjunto de dados e das tarefas de previsão.
SHAP (SHapley Additive exPlanations) é uma biblioteca que fornece explicações interpretáveis para os resultados de modelos de machine learning.
import shap
# Criaando um objeto Explainer
explainer = shap.Explainer(modelo_otimizado, X_treino)
shap_values = explainer(X_teste)
# Visualizando os valores SHAP
shap.summary_plot(shap_values, X_teste)
94%|=================== | 5818/6192 [00:11<00:00]
SHAP (SHapley Additive exPlanations) é uma poderosa ferramenta que facilita a interpretação de modelos complexos de machine learning. Em nosso exemplo, utilizamos SHAP para
No gráfico de dispersão SHAP, usando o modelo otimisado, cada ponto representa uma previsão individual. A cor dos pontos indica o valor da variável total_rooms, variando de azul (valores baixos) a rosa (valores altos). A posição no eixo x revela o impacto dessa variável nas previsões: pontos à direita (impacto positivo) indicam que valores mais altos de total_rooms tendem a aumentar a previsão do valor da casa, enquanto pontos à esquerda (impacto negativo) sugerem o contrário.
Essa visualização é crucial para entender como o modelo XGBoost está utilizando a variável total_rooms para fazer previsões, além de permitir a identificação de padrões ou possíveis outliers que podem estar influenciando as previsões de maneira inesperada.
import plotly.express as px
# Carregando o conjunto de dados usando pandas
dados_casas = pd.read_csv(url)
# Criando um gráfico de dispersão interativo
fig = px.scatter(dados_casas, x="total_rooms", y="median_house_value", title="Área da Casa vs. Preço")
fig.show()
No exemplo acima, utilizamos plotly.express para criar um gráfico de dispersão interativo que revela a relação entre o número total de cômodos (total_rooms) e o valor mediano das casas (median_house_value). Cada ponto no gráfico representa uma casa específica, onde a posição ao longo do eixo x corresponde ao número total de cômodos, enquanto a posição ao longo do eixo y reflete o preço mediano da casa.
A análise visual desse gráfico sugere que, em geral, há uma correlação positiva entre o tamanho da casa (medido pelo número de cômodos) e o valor mediano da casa. No entanto, é importante observar a distribuição dos dados, a presença de outliers e a variação dos valores. Esses elementos podem indicar situações em que a relação esperada não se mantém, possivelmente devido a outros fatores influenciando o preço das casas, como localização ou condição da propriedade.
Além disso, essa visualização interativa permite ao analista explorar os dados de maneira dinâmica, filtrando e focando em subgrupos específicos de casas, o que pode revelar padrões ou insights adicionais que não seriam imediatamente visíveis em uma análise estática.
import tensorflow as tfqq
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
# Carregando o conjunto de dados usando pandas
dados_casas = pd.read_csv(url)
# Preparando os dados
X = dados_casas[["total_rooms"]].values
y = dados_casas["median_house_value"].values
# Definindo a arquitetura do modelo
modelo_tf = Sequential([
Dense(10, activation='relu', input_shape=(X.shape[1],)),
Dense(1)
])
# Compilando o modelo
modelo_tf.compile(optimizer='adam', loss='mse')
# Treinando o modelo
modelo_tf.fit(X, y, epochs=10, batch_size=32)
# Fazendo previsões
y_pred_tf = modelo_tf.predict(X)
d_tf = modelo_tf.predict(X)
Epoch 1/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 7s 1ms/step - loss: 54861455360.0000 Epoch 2/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 829us/step - loss: 47372525568.0000 Epoch 3/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 940us/step - loss: 37677514752.0000 Epoch 4/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 767us/step - loss: 31218110464.0000 Epoch 5/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 834us/step - loss: 28638320640.0000 Epoch 6/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 752us/step - loss: 28072214528.0000 Epoch 7/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step - loss: 27703418880.0000 Epoch 8/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step - loss: 27821633536.0000 Epoch 9/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step - loss: 27955210240.0000 Epoch 10/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step - loss: 27295885312.0000 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 961us/step 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 828us/step
Neste exemplo, utilizamos `tensorflow.keras` para construir e treinar uma rede neural simples com uma camada densa (`Dense`). O modelo foi treinado para prever os preços das casas com base na variável `total_rooms` (número total de cômodos). Durante o treinamento, a rede neural ajusta os pesos das conexões para minimizar a função de perda (`loss`), que nesse caso foi definida como o erro quadrático médio (`mse`).
Após o treinamento, usamos o modelo para fazer previsões sobre os dados. No entanto, uma rede neural simples como essa pode ser propensa ao overfitting, especialmente se a arquitetura não for devidamente regularizada ou se o conjunto de dados for limitado.
Para mitigar o overfitting, podemos utilizar técnicas de regularização, como o `Dropout`, que desativa aleatoriamente uma fração das unidades durante o treinamento, obrigando a rede a não depender excessivamente de nenhuma unidade específica. Além disso, a escolha de hiperparâmetros, como o número de neurônios, a taxa de aprendizado (`learning_rate`), e o número de épocas (`epochs`), deve ser cuidadosamente ajustada para equilibrar a capacidade de aprendizado e a generalização do modelo.
A seguir, demonstramos como modificar o modelo original para incluir a camada de Dropout, que ajudará a prevenir o overfitting:
from tensorflow.keras.layers import Dropout
# Modificação da arquitetura do modelo para incluir Dropout
modelo_tf = Sequential([
Dense(10, activation='relu', input_shape=(X.shape[1],)),
Dropout(0.2), # 20% das unidades serão desligadas durante o treinamento
Dense(1)
])
# Compilando o modelo
modelo_tf.compile(optimizer='adam', loss='mse')
# Treinar o modelo com a nova arquitetura
modelo_tf.fit(X, y, epochs=10, batch_size=32)
Epoch 1/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 2s 853us/step - loss: 55741886464.0000 Epoch 2/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 855us/step - loss: 52463255552.0000 Epoch 3/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 865us/step - loss: 43733110784.0000 Epoch 4/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 881us/step - loss: 36213907456.0000 Epoch 5/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 875us/step - loss: 32650985472.0000 Epoch 6/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 819us/step - loss: 30091835392.0000 Epoch 7/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 906us/step - loss: 30268745728.0000 Epoch 8/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 792us/step - loss: 29770106880.0000 Epoch 9/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 860us/step - loss: 29637726208.0000 Epoch 10/10 645/645 ━━━━━━━━━━━━━━━━━━━━ 1s 782us/step - loss: 30105524224.0000
<keras.src.callbacks.history.History at 0x1bfc68852b0>
A adição da camada de Dropout no modelo foi essencial para mitigar o risco de overfitting durante o treinamento. Os resultados mostraram uma redução constante na perda ao longo das épocas, indicando que o modelo foi capaz de aprender de maneira controlada. Essa técnica, junto com a otimização dos hiperparâmetros, é crucial para garantir que redes neurais sejam capazes de fazer previsões precisas e confiáveis, mesmo em cenários complexos.